﻿using System;
using System.Windows;
using System.Windows.Media.Imaging;
using System.IO;
using WickedFlame.Core.State;
using System.Windows.Media;

namespace WickedFlame.Core.Shapes
{
    public class ImageShape : RectangleBase, IStateCapturable, ICutable, ICopyable
    {
        /// <summary>
        /// Serializer object description
        /// </summary>
        private const string imageDescriptor = "Img";

        /// <summary>
        /// Image that is displayed
        /// </summary>
        BitmapImage _image;

        public ImageShape()
            : base()
        {
        }

        public ImageShape(BitmapImage image) :
            base(0, 0, image.Width, image.Height)
        {
            _image = image;
        }

        public ImageShape(BitmapImage image, double x, double y) :
            base(x, y, image.Width, image.Height)
        {
            _image = image;
        }

        #region Properties

        public override int TrackerCount
        {
            get
            {
                return 0;
            }
        }

        /// <summary>
        /// Image that is displayed
        /// </summary>
        public BitmapImage Image
        {
            get
            {
                return _image;
            }
            set
            {
                _image = value;
                if (Rectangle != null)
                    Rectangle = new Rect(Rectangle.X, Rectangle.Y, _image.Width, _image.Height);
                else
                    Rectangle = new Rect(0, 0, _image.Width, _image.Height);
            }
        }

        public double Width
        {
            get
            {
                return Rectangle.Width;
            }
            set
            {
                Rectangle = new Rect(Rectangle.X, Rectangle.Y, value, Rectangle.Height);
                this.Invalidate();
            }
        }

        public double Height
        {
            get
            {
                return Rectangle.Height;
            }
            set
            {
                Rectangle = new Rect(Rectangle.X, Rectangle.Y, Rectangle.Width, value);
                this.Invalidate();
            }
        }

        public double X
        {
            get
            {
                return Rectangle.X;
            }
            set
            {
                Rectangle = new Rect(value, Rectangle.Y, Rectangle.Width, Rectangle.Height);
                this.Invalidate();
            }
        }

        public double Y
        {
            get
            {
                return Rectangle.Y;
            }
            set
            {
                Rectangle = new Rect(Rectangle.X, value, Rectangle.Width, Rectangle.Height);
                this.Invalidate();
            }
        }

        #endregion

        #region Implementation

        public override void Invalidate()
        {
            base.Invalidate();

            NotifyPropertyChanged("Width");
            NotifyPropertyChanged("Height");
            NotifyPropertyChanged("X");
            NotifyPropertyChanged("Y");

            DrawingCanvas.DrawTrackers();
        }

        public override void DrawTracker(DrawingContext drawingContext, double scale)
        {
            //Pen p = new Pen(Brushes.Blue, 1 / scale);

            drawingContext.DrawRectangle(
                Brushes.Transparent,
                new Pen(Brushes.Blue, 0.5 / scale),
                Rectangle);
        }

        

        public override void Draw(System.Windows.Media.DrawingContext drawingContext)
        {
            // rotate the shape
            if (Rotation > 0)
            {
                double centerX = Rectangle.X + (Rectangle.Width / 2);
                double centerY = Rectangle.Y + (Rectangle.Height / 2);
                drawingContext.PushTransform(new RotateTransform(Rotation, centerX, centerY));
            }

            drawingContext.DrawImage(_image, Rectangle);
        }

        public VectorShape Clone()
        {
            using (var stream = new System.IO.MemoryStream())
            {

                var encoder = new PngBitmapEncoder();
                encoder.Interlace = PngInterlaceOption.On;
                encoder.Frames.Add(BitmapFrame.Create(_image));
                encoder.Save(stream);
                stream.Seek(0, System.IO.SeekOrigin.Begin);

                var img = new BitmapImage();
                img.BeginInit();
                img.CacheOption = BitmapCacheOption.OnLoad;
                img.StreamSource = stream;
                img.EndInit();

                img.StreamSource.Dispose();

                return new ImageShape(
                    img,
                    Rectangle.X,
                    Rectangle.Y);
            }
        }

        #region Image Methods

        public BitmapImage ImageFromBuffer(byte[] bytes)
        {
            using (var stream = new MemoryStream(bytes))
            {

                var bitmapDecoder = new PngBitmapDecoder(stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
                BitmapSource src = bitmapDecoder.Frames[0]; ;

                using (var strm = new System.IO.MemoryStream())
                {

                    var encoder = new PngBitmapEncoder();
                    encoder.Interlace = PngInterlaceOption.On;
                    encoder.Frames.Add(BitmapFrame.Create(src));
                    encoder.Save(strm);
                    strm.Seek(0, System.IO.SeekOrigin.Begin);

                    var img = new BitmapImage();
                    img.BeginInit();
                    img.CacheOption = BitmapCacheOption.OnLoad;
                    img.StreamSource = strm;
                    img.EndInit();

                    return img;
                }
            }
        }

        public byte[] BufferFromImage(BitmapImage image)
        {
            var stream = new System.IO.MemoryStream();

            var encoder = new PngBitmapEncoder();
            encoder.Interlace = PngInterlaceOption.On;
            encoder.Frames.Add(BitmapFrame.Create(image));
            encoder.Save(stream);
            stream.Seek(0, System.IO.SeekOrigin.Begin);

            byte[] buffer = stream.ToArray();

            stream.Close();
            stream.Dispose();
            
            return buffer;
        }

        #endregion

        #region Static Helpers

        public static BitmapImage BitmapSourceToImage(BitmapSource source)
        {
            using (var stream = new System.IO.MemoryStream())
            {
                var encoder = new PngBitmapEncoder();
                encoder.Interlace = PngInterlaceOption.On;
                encoder.Frames.Add(BitmapFrame.Create(source));
                encoder.Save(stream);
                stream.Seek(0, System.IO.SeekOrigin.Begin);

                var img = new BitmapImage();
                img.BeginInit();
                img.CacheOption = BitmapCacheOption.OnLoad;
                img.StreamSource = stream;
                img.EndInit();

                stream.Close();
                stream.Dispose();

                return img;
            }
        }

        public static BitmapImage ShapeToScreenImage(VectorShape shape)
        {
            //var imageShape = new ImageShape();
            //int shapeIndex = canvas.SelectedLayer.ShapeList.IndexOf(shape);
            //Rect bounds = shape.Bounds;


            //set the shape to 0,0 so that the image can be drawn corectly
            //shape.Move(-(shape.Bounds.X - (shape.StrokeThickness)),
            //    -(shape.Bounds.Y - (shape.StrokeThickness)));

            var visual = new DrawingVisual();
            DrawingContext dc = visual.RenderOpen();
            shape.Draw(dc);
            dc.Close();

            using (var stream = new System.IO.MemoryStream())
            {

                var encoder = new PngBitmapEncoder();
                encoder.Interlace = PngInterlaceOption.On;
                // Create a render bitmap and push the surface to it
                var renderBitmap =
                  new RenderTargetBitmap(
                    (int)(shape.Bounds.Right + shape.StrokeThickness + shape.StrokeThickness),
                    (int)(shape.Bounds.Bottom + shape.StrokeThickness + shape.StrokeThickness),
                    96d,
                    96d,
                    System.Windows.Media.PixelFormats.Pbgra32);

                renderBitmap.Render(visual);

                encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
                encoder.Interlace = PngInterlaceOption.On;
                // save the data to the stream
                encoder.Save(stream);

                stream.Seek(0, System.IO.SeekOrigin.Begin);

                var image = new BitmapImage();
                image.BeginInit();
                image.CacheOption = BitmapCacheOption.OnLoad;
                image.StreamSource = stream;
                image.EndInit();

                return image;
            }
        }

        #endregion

        #endregion

        #region IStateCapturable Members

        public ShapeMemento CreateMementoState()
        {
            var memento = new ImageMemento();
            memento.SaveProperties(this);

            return memento;
        }

        public void ResetState(ShapeMemento memento)
        {
            memento.ResetProperties(this);
            this.Invalidate();
        }

        #endregion

        #region ICutable Member

        public void Cut(Rect rect)
        {
            WriteableBitmap bitmap = new WriteableBitmap(Image);

            Int32Rect _rect = new Int32Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight);
            // Calculate the number of bytes per pixel. 
            int _bytesPerPixel = (bitmap.Format.BitsPerPixel + 7) / 8;
            // Stride is bytes per pixel times the number of pixels.
            // Stride is the byte width of a single rectangle row.
            int _stride = bitmap.PixelWidth * _bytesPerPixel;

            // Create a byte array for a the entire size of bitmap.
            int _arraySize = _stride * bitmap.PixelHeight;

            //source pixel array
            byte[] pixelsSrc = new byte[_arraySize];
            bitmap.CopyPixels(pixelsSrc, _stride, 0);

            int bytesPerPixel = bitmap.Format.BitsPerPixel / 8;

            //copy all the pixes from the source to the destination array
            //set the alpha of the pixels contained in the rectangle to 0
            for (int row = 0; row < bitmap.PixelHeight; row += 1)
            {
                for (int col = 0; col < bitmap.PixelWidth; col += 1)
                {
                    int index = _stride * row + bytesPerPixel * col;

                    if (rect.Contains(new Point(Rectangle.X + col, Rectangle.Y + row)))
                    {
                        // set pixels to alpha = 0 if thy are in the selected rectangle
                        pixelsSrc[index] = 0xff;
                        pixelsSrc[index + 1] = 0xff;
                        pixelsSrc[index + 2] = 0xff;
                        pixelsSrc[index + 3] = 0x00;
                    }
                }
            }
            // Write out the array
            bitmap.WritePixels(_rect, pixelsSrc, _stride, 0);

            System.IO.MemoryStream strm = new System.IO.MemoryStream();
            PngBitmapEncoder encoder = new PngBitmapEncoder();
            encoder.Interlace = PngInterlaceOption.On;
            encoder.Frames.Add(BitmapFrame.Create(bitmap));
            encoder.Save(strm);
            strm.Seek(0, System.IO.SeekOrigin.Begin);

            BitmapImage img = new BitmapImage();
            img.BeginInit();
            img.CacheOption = BitmapCacheOption.OnLoad;
            img.StreamSource = strm;
            img.EndInit();

            img.StreamSource.Dispose();

            Image = img;
            this.Invalidate();

            //notify all subscribed objects
            CallChangedHandler();
        }

        #endregion

        #region ICopyable Member

        //public VectorShape Copy(Rect rectangle)
        public VectorShape Copy()
        {
            //if (Selected)
                return Clone();

            //Rect intersection = new Rect(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
            //intersection.Intersect(Rectangle);

            //Int32Rect rect = new Int32Rect(
            //    (int)(intersection.X - Rectangle.X), 
            //    (int)(intersection.Y - Rectangle.Y), 
            //    (int)intersection.Width, (
            //    int)intersection.Height);

            //CroppedBitmap cb = new CroppedBitmap(image, rect);
            
            ////cb.Format = System.Windows.Media.PixelFormats.Pbgra32;

            //return new ImageShape(BitmapSourceToImage(cb), intersection.X, intersection.Y);
        }

        #endregion

        #region TEST!!!

        
        public void MergeImage(BitmapImage image, Rect rectangle)
        {
            DrawingVisual visual = new DrawingVisual();
            DrawingContext dc = visual.RenderOpen();
            dc.DrawImage(Image, new Rect(0, 0, Rectangle.Width, Rectangle.Height));
            dc.DrawImage(image, new Rect(rectangle.X - Rectangle.X, rectangle.Y - Rectangle.Y, rectangle.Width, rectangle.Height));
            dc.Close();

            System.IO.MemoryStream stream = new System.IO.MemoryStream();

            PngBitmapEncoder encoder = new PngBitmapEncoder();
            encoder.Interlace = PngInterlaceOption.On;
            // Create a render bitmap and push the surface to it
            RenderTargetBitmap renderBitmap =
              new RenderTargetBitmap(
                (int)Image.Width,
                (int)Image.Height,
                96d,
                96d,
                System.Windows.Media.PixelFormats.Pbgra32);

            renderBitmap.Render(visual);

            encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
            encoder.Interlace = PngInterlaceOption.On;
            // save the data to the stream
            encoder.Save(stream);

            stream.Seek(0, System.IO.SeekOrigin.Begin);

            BitmapImage img2 = new BitmapImage();
            img2.BeginInit();
            img2.CacheOption = BitmapCacheOption.OnLoad;
            img2.StreamSource = stream;
            img2.EndInit();

            Image = img2;
            this.Invalidate();
        }

        public BitmapImage CreateBlankBitmapImage(Rect rectangle, byte b)
        {
            int width = (int)rectangle.Width;
            int height = (int)rectangle.Height;
            System.Windows.Media.PixelFormat pf =
                System.Windows.Media.PixelFormats.Pbgra32;
            int bpp = pf.BitsPerPixel;// 4;

            //System.Collections.Generic.List<Color> cl = new System.Collections.Generic.List<Color>();
            //cl.Add(Colors.Transparent);
            //BitmapPalette bp = new BitmapPalette(cl);

            //These are 'dummy' pixels
            //only used to create our bitmap
            //further editing after bitmap creation 
            //of these pixels does nothing, that is
            //what this hack is for
            byte[] pixels = new byte[width * height * bpp];

            //for (int i = 0; i < width * height * bpp; ++i)
            //{
            //    pixels[i] = b;
            //}

            //Create a new bitmap source
            BitmapSource src = BitmapSource.Create(width,
                                                      height,
                                                      96,
                                                      96,
                                                      System.Windows.Media.PixelFormats.Bgr32,
                                                      null, //bp,//
                                                      pixels,
                                                      width * bpp);

            System.IO.MemoryStream strm = new System.IO.MemoryStream();
            PngBitmapEncoder enc = new PngBitmapEncoder();
            enc.Interlace = PngInterlaceOption.On;
            enc.Frames.Add(BitmapFrame.Create(src));
            enc.Save(strm);
            strm.Seek(0, System.IO.SeekOrigin.Begin);

            BitmapImage blankImage = new BitmapImage();
            blankImage.BeginInit();
            blankImage.CacheOption = BitmapCacheOption.OnLoad;
            blankImage.StreamSource = strm;
            blankImage.EndInit();

            blankImage.StreamSource.Dispose();

            return blankImage;
        }

        public void Distort(double amount)
        {
            WriteableBitmap bitmap = new WriteableBitmap(Image);

            Int32Rect _rect = new Int32Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight);
            // Calculate the number of bytes per pixel. 
            int _bytesPerPixel = (bitmap.Format.BitsPerPixel + 7) / 8;
            // Stride is bytes per pixel times the number of pixels.
            // Stride is the byte width of a single rectangle row.
            int _stride = bitmap.PixelWidth * _bytesPerPixel;

            // Create a byte array for a the entire size of bitmap.
            int _arraySize = _stride * bitmap.PixelHeight;

            byte[] pixelsSrc = new byte[_arraySize];
            byte[] pixelsNew = new byte[_arraySize];

            bitmap.CopyPixels(pixelsSrc, _stride, 0);

            int width = bitmap.PixelWidth;
            int height = bitmap.PixelHeight;
            int xCenter = width / 2;
            int yCenter = height / 2;
            int bytesPerPixel = bitmap.Format.BitsPerPixel / 8;

            for (int row = 0; row < bitmap.PixelHeight; row += 1)
            {
                for (int col = 0; col < bitmap.PixelWidth; col += 1)
                {
                    // Calculate length of point to center and angle
                    int xDelta = col - xCenter;
                    int yDelta = row - yCenter;
                    double distanceToCenter = Math.Sqrt(xDelta * xDelta +
                                                        yDelta * yDelta);
                    double angleClockwise = Math.Atan2(yDelta, xDelta);

                    // Calculate angle of rotation for twisting effect 
                    double xEllipse = xCenter * Math.Cos(angleClockwise);
                    double yEllipse = yCenter * Math.Sin(angleClockwise);
                    double radius = Math.Sqrt(xEllipse * xEllipse +
                                              yEllipse * yEllipse);
                    double fraction = Math.Max(0, 1 - distanceToCenter / radius);
                    double twist = fraction * Math.PI * amount / 180;

                    // Calculate the source pixel for each destination pixel
                    int colSrc = (int)(xCenter + (col - xCenter) * Math.Cos(twist) +
                        (row - yCenter) * Math.Sin(twist));
                    int rowSrc = (int)(yCenter + (col - xCenter) *
                                        Math.Sin(twist)
                        + (row - yCenter) * Math.Cos(twist));
                    colSrc = Math.Max(0, Math.Min(width - 1, colSrc));
                    rowSrc = Math.Max(0, Math.Min(height - 1, rowSrc));

                    // Calculate the indices
                    int index = _stride * row + bytesPerPixel * col;
                    int indexSrc = _stride * rowSrc + bytesPerPixel * colSrc;

                    // Transfer the pixels
                    for (int i = 0; i < bytesPerPixel; i++)
                        pixelsNew[index + i] = pixelsSrc[indexSrc + i];
                }
            }
            // Write out the array
            bitmap.WritePixels(_rect, pixelsNew, _stride, 0);
            //bitmap.WritePixels(_rect, pixelsSrc, _stride, 0);

            System.IO.MemoryStream strm = new System.IO.MemoryStream();
            PngBitmapEncoder encoder = new PngBitmapEncoder();//BmpBitmapEncoder();//
            encoder.Interlace = PngInterlaceOption.On;
            encoder.Frames.Add(BitmapFrame.Create(bitmap));
            encoder.Save(strm);
            strm.Seek(0, System.IO.SeekOrigin.Begin);

            BitmapImage img = new BitmapImage();
            img.BeginInit();
            img.CacheOption = BitmapCacheOption.OnLoad;
            img.StreamSource = strm;
            img.EndInit();

            img.StreamSource.Dispose();

            Image = img;
            this.Invalidate();

            CallChangedHandler();
        }

        //public BitmapImage Crop(Rect rectangle)
        //{
        //    Int32Rect rect = new Int32Rect((int)rectangle.X, (int)rectangle.Y, (int)rectangle.Width, (int)rectangle.Height);
        //    //Array a;
        //    //int stride = 0;
        //    //int t = 0;
        //    //Image.CopyPixels(rect, a, stride, t);
            
        //    CroppedBitmap cb = new CroppedBitmap((BitmapSource)Image, rect);

        //    Image.

        //    return new BitmapImage();
        //}

        //public static void split(Image img, int portionHeight, int portionWidth, string path, string fileName)
        //{
        //    Bitmap imgSplitted = new Bitmap(portionWidth, portionHeight);
        //    Graphics gr = Graphics.FromImage(imgSplitted);

        //    for (int y = 0; y < img.Height; y += portionHeight)
        //        for (int x = 0; x < img.Width; x += portionWidth)
        //        {
        //            gr.Clear(Color.White);
        //            gr.DrawImage(img, new Rectangle(0, 0, imgSplitted.Width, imgSplitted.Height), x, y, portionWidth,
        //                         portionHeight, GraphicsUnit.Pixel);
        //            gr.Save();
        //            imgSplitted.Save(Path.Combine(path, fileName + x.ToString() + "_" + y.ToString() + ".png"));
        //        }
        //}

        //private static Image resizeImage(Image imgToResize, Size size)
        //{
        //    int sourceWidth = imgToResize.Width;
        //    int sourceHeight = imgToResize.Height;

        //    float nPercent = 0;
        //    float nPercentW = 0;
        //    float nPercentH = 0;

        //    nPercentW = ((float)size.Width / (float)sourceWidth);
        //    nPercentH = ((float)size.Height / (float)sourceHeight);

        //    if (nPercentH < nPercentW)
        //        nPercent = nPercentH;
        //    else
        //        nPercent = nPercentW;

        //    int destWidth = (int)(sourceWidth * nPercent);
        //    int destHeight = (int)(sourceHeight * nPercent);

        //    Bitmap b = new Bitmap(destWidth, destHeight);
        //    Graphics g = Graphics.FromImage((Image)b);
        //    g.InterpolationMode = InterpolationMode.HighQualityBicubic;

        //    g.DrawImage(imgToResize, 0, 0, destWidth, destHeight);
        //    g.Dispose();

        //    return (Image)b;
        //}

        #endregion
        
    }
}
